package com.github.gaojh.server.handler;

import com.github.gaojh.config.Environment;
import com.github.gaojh.mvc.websocket.WebSocketContext;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * @author gaojh
 * @since 2020/3/23
 */
@Slf4j
public class WebSocketServerHandler extends ChannelInboundHandlerAdapter {

    private WebSocketServerHandshaker handshaker;
    private WebSocketAdapter webSocketAdapter;
    private String url;
    private StringBuilder frameBuffer;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
            handleHttpRequest(ctx, fullHttpRequest);
        } else if (msg instanceof WebSocketFrame) {
            handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
        } else {
            ReferenceCountUtil.retain(msg);
            ctx.fireChannelRead(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        if (webSocketAdapter != null) {
            webSocketAdapter.onException(ctx, cause);
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if (webSocketAdapter != null) {
            webSocketAdapter.onClose(ctx);
        }
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
        if (isWebSocketRequest(fullHttpRequest)) {
            WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:" + Environment.me().getPort(), null, false, 65535000);
            handshaker = wsFactory.newHandshaker(fullHttpRequest);
            if (handshaker == null || !Environment.me().isEnableWebsocket()) {
                WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
            } else {
                QueryStringDecoder decoder = new QueryStringDecoder(fullHttpRequest.uri());
                url = decoder.path();
                webSocketAdapter = WebSocketContext.getWebSocketAdapter(url);
                handshaker.handshake(ctx.channel(), fullHttpRequest);
                if (webSocketAdapter != null) {
                    webSocketAdapter.onHandshake(ctx, fullHttpRequest);
                } else {
                    ctx.writeAndFlush(new TextWebSocketFrame("未找到对应的WebSocket处理器：" + url));
                }
            }
        } else {
            ReferenceCountUtil.retain(fullHttpRequest);
            ctx.fireChannelRead(fullHttpRequest);
        }
    }

    private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
        // 判断是否关闭链路的指令
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        // 判断是否ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }

        if (frame instanceof TextWebSocketFrame) {
            frameBuffer = new StringBuilder();
            frameBuffer.append(((TextWebSocketFrame) frame).text());
        } else if (frame instanceof ContinuationWebSocketFrame) {
            if (frameBuffer != null) {
                frameBuffer.append(((ContinuationWebSocketFrame) frame).text());
            } else {
                log.warn("Continuation frame received without initial frame.");
            }
        } else {
            throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass().getName()));
        }

        // Check if Text or Continuation Frame is final fragment and handle if needed.
        if (frame.isFinalFragment()) {
            String request = frameBuffer.toString();
            if (webSocketAdapter != null) {
                webSocketAdapter.onMessage(ctx, request);
            } else {
                ctx.writeAndFlush(new TextWebSocketFrame("未找到对应的WebSocket处理器：" + url));
            }

            frameBuffer = null;
        }


        /*// 本例程仅支持文本消息，不支持二进制消息
        if (!(frame instanceof TextWebSocketFrame)) {
            throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass().getName()));
        }
        // 处理消息
        String request = ((TextWebSocketFrame) frame).text();
        if (webSocketAdapter != null) {
            webSocketAdapter.onMessage(ctx, request);
        } else {
            ctx.writeAndFlush(new TextWebSocketFrame("未找到对应的WebSocket处理器：" + url));
        }*/
    }

    private boolean isWebSocketRequest(HttpRequest req) {
        return req != null && req.decoderResult().isSuccess() && "websocket".equals(req.headers().get("Upgrade"));
    }
}
